<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8" />
    <title>修复版：漂亮的 3D 投骰子（可直接双击运行）</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <style>
        :root{--bg1:#071428;--card:rgba(255,255,255,0.04);--accent:#ffb86b}
        *{box-sizing:border-box}
        html,body{height:100%;margin:0;font-family:"Microsoft YaHei",Segoe UI,Roboto,sans-serif}
        body{
            background: linear-gradient(135deg,#071428 0%, #0f172a 40%, #07203f 100%);
            color:#fff;
            display:flex;align-items:center;justify-content:center;padding:24px;
        }

        .app{width:100%;max-width:1100px;display:grid;grid-template-columns:520px 1fr;gap:20px}
        @media(max-width:980px){.app{grid-template-columns:1fr}}

        .panel{background:var(--card);padding:16px;border-radius:12px;box-shadow:0 8px 30px rgba(2,6,23,0.6)}
        header{grid-column:1/-1;display:flex;align-items:center;justify-content:space-between;gap:12px}
        header h1{margin:0;font-size:1.25rem}
        header p{margin:0;color:rgba(255,255,255,0.75);font-size:0.9rem}

        #canvasWrap{height:520px;border-radius:10px;overflow:hidden;background:linear-gradient(180deg, rgba(255,255,255,0.02), transparent);display:flex;align-items:center;justify-content:center}
        #three-root{width:100%;height:100%}

        .controls{display:flex;gap:10px;margin-top:12px;flex-wrap:wrap}
        .btn{padding:10px 14px;border-radius:999px;border:0;background:linear-gradient(90deg,#ff8a65,#ff6b6b);color:#fff;font-weight:700;cursor:pointer}
        .btn.ghost{background:transparent;border:1px solid rgba(255,255,255,0.06);color:rgba(255,255,255,0.9)}
        .small{font-size:0.85rem;color:rgba(255,255,255,0.78)}

        .right .historyList{max-height:460px;overflow:auto;margin-top:12px}
        .historyItem{display:flex;align-items:center;justify-content:space-between;padding:8px;border-radius:8px;margin-bottom:8px;background:linear-gradient(90deg, rgba(255,255,255,0.02), transparent)}
        .dot{width:44px;height:44px;border-radius:8px;background:rgba(0,0,0,0.24);display:flex;align-items:center;justify-content:center;font-weight:800;font-size:1.1rem}
        .meta{font-size:0.85rem;color:rgba(255,255,255,0.75)}
        footer{grid-column:1/-1;text-align:center;color:rgba(255,255,255,0.45);font-size:0.85rem;padding-top:6px}
    </style>
</head>
<body>
<div class="app">
    <header>
        <div>
            <h1>🎲 3D 投骰子（修复版）</h1>
            <p>不需要构建工具，直接双击打开；点击骰子或按钮都可掷骰；历史保存在 localStorage。</p>
        </div>
        <div class="small">提示：如果想用后端决定点数，我可以把请求代码加上（你只需提供 API）。</div>
    </header>

    <div class="panel">
        <strong>投掷区</strong>
        <div id="canvasWrap" style="margin-top:12px">
            <div id="three-root"></div>
        </div>

        <div class="controls">
            <button class="btn" id="rollBtn">掷骰子</button>
            <button class="btn ghost" id="resetBtn">重置</button>
            <button class="btn ghost" id="styleBtn">切换样式</button>
            <button class="btn ghost" id="clearHistory">清空历史</button>
        </div>

        <div style="margin-top:12px" class="small" id="resultText">准备就绪 · 点击“掷骰子”或点按骰子</div>
    </div>

    <div class="panel right">
        <div style="display:flex;justify-content:space-between;align-items:center">
            <strong>历史记录</strong>
            <div class="small">总数：<span id="count">0</span></div>
        </div>

        <div style="margin-top:8px" class="small">当前样式：<span id="styleName">经典白</span></div>

        <div class="historyList" id="historyList"></div>
    </div>

    <footer class="panel">代码说明：使用非模块版 three + OrbitControls（避免裸导入问题），动画通过 quaternion slerp 平滑对齐到目标面。</footer>
</div>

<!-- 使用非模块版 Three.js 和 非模块 OrbitControls（全局 THREE） -->
<script src="https://unpkg.com/three@0.152.2/build/three.min.js"></script>
<script src="https://unpkg.com/three@0.152.2/examples/js/controls/OrbitControls.js"></script>

<script>
    (function(){
        // ---------- 配置 ----------
        const styles = [
            { name: '经典白', base:'#ffffff', dot:'#000000' },
            { name: '奢华红', base:'#8B0000', dot:'#FFD700' },
            { name: '宝石蓝', base:'#00308F', dot:'#AFEEEE' }
        ];
        let currentStyle = 0;
        const HISTORY_KEY = 'dice_history_v3';

        // ---------- Three.js 基础 ----------
        const root = document.getElementById('three-root');
        const W = root.clientWidth || 520, H = root.clientHeight || 520;
        const scene = new THREE.Scene();
        scene.background = new THREE.Color(0x071428);

        const camera = new THREE.PerspectiveCamera(45, W/H, 0.1, 1000);
        camera.position.set(3.2, 3.8, 4.8);

        const renderer = new THREE.WebGLRenderer({ antialias:true, alpha:true });
        renderer.setPixelRatio(window.devicePixelRatio || 1);
        renderer.setSize(root.clientWidth, root.clientHeight);
        root.appendChild(renderer.domElement);

        const controls = new THREE.OrbitControls(camera, renderer.domElement);
        controls.enablePan = false;
        controls.enableZoom = true;
        controls.target.set(0,0.5,0);

        const dir = new THREE.DirectionalLight(0xffffff, 1.0);
        dir.position.set(5,8,5);
        scene.add(dir);
        scene.add(new THREE.AmbientLight(0xffffff, 0.35));

        // 地面
        const plane = new THREE.Mesh(
            new THREE.PlaneGeometry(20,20),
            new THREE.MeshStandardMaterial({ color:0x0f1724, metalness:0.15, roughness:0.85 })
        );
        plane.rotation.x = -Math.PI/2; plane.position.y = -0.9;
        scene.add(plane);

        // 一些装饰
        for(let i=0;i<6;i++){
            const g = new THREE.BoxGeometry(0.35,0.35,0.35);
            const m = new THREE.MeshStandardMaterial({ color: Math.random()*0xffffff, opacity:0.6, transparent:true, metalness:0.6, roughness:0.2});
            const mesh = new THREE.Mesh(g,m);
            mesh.position.set((Math.random()-0.5)*6, (Math.random()-0.5)*1+0.8, (Math.random()-0.5)*4);
            mesh.rotation.set(Math.random(),Math.random(),Math.random());
            scene.add(mesh);
        }

        // ---------- 骰子（网格） ----------
        let cube = null;
        function makeFaceCanvas(value, baseColor, dotColor){
            const size=512;
            const c=document.createElement('canvas');
            c.width=c.height=size;
            const ctx=c.getContext('2d');
            ctx.fillStyle=baseColor; ctx.fillRect(0,0,size,size);
            ctx.lineWidth=18; ctx.strokeStyle='rgba(0,0,0,0.16)';
            ctx.strokeRect(12,12,size-24,size-24);
            ctx.fillStyle=dotColor;
            const p={
                1:[[size/2,size/2]],
                2:[[size*0.3,size*0.3],[size*0.7,size*0.7]],
                3:[[size*0.3,size*0.3],[size/2,size/2],[size*0.7,size*0.7]],
                4:[[size*0.3,size*0.3],[size*0.7,size*0.3],[size*0.3,size*0.7],[size*0.7,size*0.7]],
                5:[[size*0.3,size*0.3],[size*0.7,size*0.3],[size/2,size/2],[size*0.3,size*0.7],[size*0.7,size*0.7]],
                6:[[size*0.3,size*0.2],[size*0.7,size*0.2],[size*0.3,size*0.5],[size*0.7,size*0.5],[size*0.3,size*0.8],[size*0.7,size*0.8]]
            };
            const r=Math.floor(size*0.06);
            (p[value]||p[1]).forEach(pt=>{
                ctx.beginPath(); ctx.arc(pt[0],pt[1],r,0,Math.PI*2); ctx.fill();
            });
            return new THREE.CanvasTexture(c);
        }

        function createCube(){
            if(cube){
                cube.geometry.dispose();
                cube.material.forEach(m=>{ if(m.map) m.map.dispose(); m.dispose && m.dispose(); });
                scene.remove(cube);
                cube = null;
            }
            const s = styles[currentStyle];
            // Box faces order: +X, -X, +Y, -Y, +Z, -Z
            const mats = [
                new THREE.MeshStandardMaterial({ map: makeFaceCanvas(3, s.base, s.dot), metalness:0.4, roughness:0.4 }), // +X -> 3
                new THREE.MeshStandardMaterial({ map: makeFaceCanvas(4, s.base, s.dot), metalness:0.4, roughness:0.4 }), // -X -> 4
                new THREE.MeshStandardMaterial({ map: makeFaceCanvas(1, s.base, s.dot), metalness:0.4, roughness:0.4 }), // +Y -> 1
                new THREE.MeshStandardMaterial({ map: makeFaceCanvas(6, s.base, s.dot), metalness:0.4, roughness:0.4 }), // -Y -> 6
                new THREE.MeshStandardMaterial({ map: makeFaceCanvas(2, s.base, s.dot), metalness:0.4, roughness:0.4 }), // +Z -> 2
                new THREE.MeshStandardMaterial({ map: makeFaceCanvas(5, s.base, s.dot), metalness:0.4, roughness:0.4 })  // -Z -> 5
            ];
            const geo = new THREE.BoxGeometry(1,1,1);
            cube = new THREE.Mesh(geo, mats);
            cube.position.set(0,0.7,0);
            cube.rotation.set(-0.15,0.35,0.05);
            scene.add(cube);
            document.getElementById('styleName').textContent = styles[currentStyle].name;
        }
        createCube();

        // ---------- 目标面对应四元数（朝上） ----------
        // 我们的面映射：+Y=1, -Y=6, +Z=2, -Z=5, +X=3, -X=4
        const faceEuler = {
            1: new THREE.Euler(0,0,0),
            2: new THREE.Euler(-Math.PI/2,0,0),
            5: new THREE.Euler(Math.PI/2,0,0),
            3: new THREE.Euler(0,0,Math.PI/2),
            4: new THREE.Euler(0,0,-Math.PI/2),
            6: new THREE.Euler(Math.PI,0,0)
        };
        const faceQuat = {};
        for(const k in faceEuler){ const q=new THREE.Quaternion(); q.setFromEuler(faceEuler[k]); faceQuat[k]=q; }

        // ---------- 投掷动画（两阶段：急速旋转 -> 平滑对齐） ----------
        let rolling = false;
        function easeOutCubic(t){ return 1 - Math.pow(1-t,3); }

        async function rollDice(){
            if(!cube || rolling) return;
            rolling = true;
            setResult('掷骰中...');

            // stage 1: 快速翻滚（仅视觉）
            const fastDuration = 700 + Math.random()*400; // ms
            const extraRot = {
                x: Math.PI*(4 + Math.floor(Math.random()*4)),
                y: Math.PI*(4 + Math.floor(Math.random()*4)),
                z: Math.PI*(4 + Math.floor(Math.random()*4))
            };
            const startRot = cube.rotation.clone();
            const startTime = performance.now();
            // run fast rotation animation with requestAnimationFrame
            await new Promise(resolve => {
                function frame(now){
                    const t = Math.min(1, (now - startTime)/fastDuration);
                    const eased = easeOutCubic(t);
                    cube.rotation.x = startRot.x + extraRot.x * eased;
                    cube.rotation.y = startRot.y + extraRot.y * eased;
                    cube.rotation.z = startRot.z + extraRot.z * eased;
                    if(t<1) requestAnimationFrame(frame);
                    else resolve();
                }
                requestAnimationFrame(frame);
            });

            // 获取结果（这里模拟后端延迟，若你有后端接口可替换）
            const res = await simulateServerResult();

            // stage 2: 平滑 slerp 到目标面（并加上整圈旋转）
            const value = res.value;
            const baseTarget = faceQuat[value].clone();

            // 给目标乘以若干整圈，避免落得太“呆板”
            const extraQuat = new THREE.Quaternion().setFromEuler(new THREE.Euler(
                Math.PI * (2*(1 + Math.floor(Math.random()*2))),
                Math.PI * (2*(1 + Math.floor(Math.random()*2))),
                Math.PI * (2*(1 + Math.floor(Math.random()*2)))
            ));
            baseTarget.multiply(extraQuat);

            const startQuat = cube.quaternion.clone();
            const duration2 = 900 + Math.random()*600;
            const st = performance.now();
            await new Promise(resolve => {
                function f(now){
                    const tt = Math.min(1, (now - st)/duration2);
                    const e = easeOutCubic(tt);
                    THREE.Quaternion.slerp(startQuat, baseTarget, cube.quaternion, e);
                    if(tt < 1) requestAnimationFrame(f);
                    else resolve();
                }
                requestAnimationFrame(f);
            });

            // 做一点小震动 / 微调（视觉上更自然）
            await smallSettle();

            // 完成
            addHistory(value);
            setResult(`🎯 结果：${value}`);
            rolling = false;
        }

        function smallSettle(){
            return new Promise(resolve=>{
                const amp = 0.06, dur=260, st=performance.now();
                const initRot = cube.rotation.clone();
                function f(now){
                    const t = (now - st)/dur;
                    if(t>1){ cube.rotation.copy(initRot); resolve(); return; }
                    const wobble = Math.sin(t * Math.PI * 4) * (1 - t) * amp;
                    cube.rotation.x = initRot.x + wobble;
                    cube.rotation.z = initRot.z - wobble;
                    requestAnimationFrame(f);
                }
                requestAnimationFrame(f);
            });
        }

        // 模拟后端（延迟并返回 1..6）
        function simulateServerResult(){
            return new Promise(resolve => {
                setTimeout(()=> resolve({ success:true, value: Math.floor(Math.random()*6)+1 }), 300 + Math.random()*500);
            });
        }

        // ---------- UI 绑定与历史 ----------
        const rollBtn = document.getElementById('rollBtn');
        const resetBtn = document.getElementById('resetBtn');
        const styleBtn = document.getElementById('styleBtn');
        const clearBtn = document.getElementById('clearHistory');
        const resultEl = document.getElementById('resultText');

        rollBtn.addEventListener('click', ()=> rollDice());
        resetBtn.addEventListener('click', ()=>{
            if(!cube) return;
            cube.position.set(0,0.7,0);
            cube.rotation.set(-0.15,0.35,0.05);
            cube.quaternion.setFromEuler(cube.rotation);
            setResult('骰子已重置');
        });
        styleBtn.addEventListener('click', ()=> {
            currentStyle = (currentStyle+1) % styles.length;
            createCube();
            setResult('样式切换为：' + styles[currentStyle].name);
        });
        clearBtn.addEventListener('click', ()=>{
            if(confirm('确认清空历史？')) { localStorage.removeItem(HISTORY_KEY); renderHistory(); }
        });

        // 点击骰子也掷骰
        const raycaster = new THREE.Raycaster();
        const pointer = new THREE.Vector2();
        renderer.domElement.addEventListener('pointerdown', (ev)=>{
            pointer.x = (ev.offsetX / renderer.domElement.clientWidth) * 2 - 1;
            pointer.y = - (ev.offsetY / renderer.domElement.clientHeight) * 2 + 1;
            raycaster.setFromCamera(pointer, camera);
            const hits = raycaster.intersectObject(cube, true);
            if(hits.length>0){
                rollDice();
            }
        });

        // 历史函数
        function loadHistory(){ try{ const raw=localStorage.getItem(HISTORY_KEY); return raw?JSON.parse(raw):[] }catch(e){return[]}}
        function saveHistory(arr){ localStorage.setItem(HISTORY_KEY, JSON.stringify(arr)) }
        function addHistory(val){
            const arr = loadHistory();
            arr.unshift({ value: val, time: new Date().toISOString() });
            if(arr.length>200) arr.length = 200;
            saveHistory(arr);
            renderHistory();
        }
        function renderHistory(){
            const wrap = document.getElementById('historyList');
            const arr = loadHistory();
            wrap.innerHTML = '';
            document.getElementById('count').textContent = arr.length;
            if(arr.length===0){ wrap.innerHTML = '<div class="small" style="padding:10px;color:rgba(255,255,255,0.55)">暂无记录，开始掷骰吧！</div>'; return; }
            for(const it of arr){
                const d = new Date(it.time);
                const item = document.createElement('div');
                item.className = 'historyItem';
                item.innerHTML = `<div style="display:flex;gap:12px;align-items:center">
          <div class="dot">${it.value}</div>
          <div>
            <div style="font-weight:700">点数 ${it.value}</div>
            <div class="meta">${d.toLocaleString()}</div>
          </div>
        </div>
        <div class="small">${remark(it.value)}</div>`;
                wrap.appendChild(item);
            }
        }
        function remark(v){ return {1:'🐢 慢',2:'✨ 好',3:'👍 中',4:'🙂 好',5:'🔥 幸运',6:'💥 大吉'}[v]||'' }

        function setResult(txt){ resultEl.textContent = txt }

        // ---------- 渲染循环 ----------
        function animate(){
            requestAnimationFrame(animate);
            controls.update();
            renderer.render(scene, camera);
        }
        animate();
        renderHistory();

        // 窗口自适应
        window.addEventListener('resize', ()=>{
            const w = root.clientWidth, h = root.clientHeight;
            renderer.setSize(w,h);
            camera.aspect = w/h; camera.updateProjectionMatrix();
        });

    })();
</script>
</body>
</html>
